/*-
 * See the file LICENSE for redistribution information.
 *
 * Copyright (c) 2005, 2011 Oracle and/or its affiliates.  All rights reserved.
 *
 * $Id$ 
 */

// File: MyDb.hpp

#ifndef MYDB_H
#define MYDB_H

#include <string>
#include <vector>
#include <map>
#include <unordered_map>
#include <unordered_set>
#include <cassert>
//#include "db_cxx.h"
#include "kchashdb.h"
//#include "boost/functional/hash.hpp"
#include "global_def.h"

using namespace std;
using namespace kyotocabinet;


typedef std::vector<OBJID> RowHash;
typedef RowHash::iterator RHIter;

void initDbEnv();

//DbEnv * getDbEnv();

struct IdInfo{
  std::string name;
  OBJID parent;
  IdInfo(const string &_n, OBJID _p):name(_n),parent(_p){}
  IdInfo():name(""),parent(0){};
};


class IdManager{
  private:
	static IdManager * _instance;
	OBJID colid;
	OBJID tableid;
	std::unordered_map<std::string, OBJID> name2id;
	std::unordered_map<OBJID, IdInfo> id2name;

  protected:
	IdManager():colid(1),tableid(TABLE_NUM_START){};
  public:

	static IdManager * getInstance(){
	  if ( _instance == NULL){
		_instance = new IdManager();
	  }
    	return _instance;
	}

	OBJID addColumn(const std::string &name, OBJID tid){
	  assert(colid<TABLE_NUM_START);
	  OBJID id = colid++;
	  name2id[name] = id;
	  id2name[id] = IdInfo(name,tid);
	  return id;
	}

	OBJID addTable(const std::string &name){
	  OBJID id = tableid++;
	  name2id[name] = id;
	  id2name[id] = IdInfo(name,0);
	  return id;
	}

	std::string getNameById(OBJID id){
	  std::unordered_map<OBJID, IdInfo>::iterator iter = id2name.find(id);
	  assert(iter!=id2name.end());
	  return iter->second.name;
	}

	const IdInfo & getInfoById(OBJID id){
	  std::unordered_map<OBJID, IdInfo>::iterator iter = id2name.find(id);
	  assert(iter!=id2name.end());
	  return iter->second;
	}

	OBJID getIdByName(const std::string &name){
	  //cout<<"@@ "<<name<<endl;
	  std::unordered_map<std::string, OBJID>::iterator iter = name2id.find(name);
	  assert(iter!=name2id.end());
	  return iter->second;
	}

	std::vector<OBJID> getAllId(const std::vector<std::string> &names){
	  std::vector<OBJID> ids;
	  for(int i=0;i<names.size();i++){
		ids.push_back(getIdByName(names[i]));
	  }
	  return ids;
	}

};

class IndexPr{
  public:
  std::unordered_map<string, OBJID> data;
  void insert(const string& key,OBJID id){
     data[key] = id;
  }
};

template <typename T>
class IndexDb{
public:
    typedef std::map<T, vector<OBJID> > IDBType;
    //std::multimap< T,OBJID> data;
	IDBType data;
    typedef typename IDBType::iterator Iter;
    typedef typename IDBType::reverse_iterator RIter;

	//std::unordered_multimap<T,OBJID> hit_data;
    //typedef typename unordered_multimap<T , OBJID>::iterator UM_Iter;

	OBJID myid;

	//stat
	int distinct_cnt;
	int size;
	double sum;

    //std::unordered_multimap<T,OBJID  > hit_data;
	//typedef typename std::unordered_multimap<T,OBJID>::iterator UM_Iter;
    T getMin(){
        if(data.size())
            return data.begin()->first;
        else
            return T();
    }
    T getMax(){
        if(data.size())
            return data.rbegin()->first;
        else
            return T();
    }

	IndexDb(OBJID _myid){
      //hit_data.max_load_factor(4.0f);
	  myid = _myid;
	  size = 0;
	  distinct_cnt = 0;
	}

   void insert(const pair<T,OBJID> &p){
	 //cout<<"@@ "<<IdManager::getInstance()->getNameById(myid)<<' '<<p.first<<endl;
	 Iter iter = data.find(p.first);
	 if(iter==data.end()){
	   distinct_cnt++;
	   data.insert(pair<T,vector<OBJID> >(p.first,vector<OBJID>(1,p.second)) );
	 }else{
	   iter->second.push_back(p.second);
	 }
	 size++;
	 //hit_data.insert(p);
   }

   int getSize(){
	 return size;
   }

   void dumpStat(){
	 IdManager * im = IdManager::getInstance();
	 cout<<"IDXDB STAT for "<<im->getNameById(myid)<<':'<<endl;
     cout<<"size: "<<size<<endl;
	 cout<<"distinct count: "<<distinct_cnt<<endl;
	 cout<<"min : "<<getMin()<<endl;
	 cout<<"max : "<<getMax()<<endl;
	 //dumpData();
	 cout<<"----------"<<endl;
     
   }

    void dumpData(){
        Iter iter = data.begin();
        for(;iter!=data.end();++iter){
		  for(int i=0;i<iter->second.size();i++)
            cerr<<iter->first<<'\t'<<iter->second[i]<<endl;
        }
    }
};


double evalPredicate(const CondInfo  &condInfo);
bool cmpPredicate_slow1(const CondInfo &ci1, const CondInfo &ci2);


class MyDb:public TreeDB
{
public:
    // Constructor requires a path to the database,
    // and a database name.
    struct _ColIden{
        MyDb *db;
        int idx;
        _ColIden(MyDb *_db, int _idx):db(_db),idx(_idx){}
    };

    MyDb( const std::string & table_name, 
		const std::vector<std::string> &column_name, 
		const std::vector<std::string> &type, const std::vector<std::string> &pkey);

    // Our destructor just calls our private close method.
    ~MyDb() { closedb(); }
    std::vector<OBJID> columns;
    map<OBJID, int> pkeyIdx;	
	vector<int> pkey2origrow;
	OBJID pkcolid;
	
    
    std::unordered_map<string, OBJID> pkey_map;
	std::vector<int> reorder;//insert should interate on this
	int varlen_cnt;
	int constlen_cnt;
	int record_max_size;
    int value;
	OBJID tableId;
    //vector<_byte *> rows;

    string extractPKey(const std::vector<std::string> &row0);

        //!!! row is in original order
	OBJID insertRow(const std::vector<std::string> &row);

	inline OFFSET getConstOffset(){
	  return sizeof(OBJID)+varlen_cnt*sizeof(OFFSET);
	}

	inline OFFSET getVarOffset(){
	  return getConstOffset()+sizeof(int)*constlen_cnt;
	}

    void dumpRows();
    bool fetchRow(OBJID rid,const _byte **buf);

//id/idx in the following functions are column objid/idx
    inline int getIntByIndex(int idx, const _byte *buf){
      // assert(idx<constlen_cnt);
       return *(int*)(buf+getConstOffset()+sizeof(int)*idx);
    }
    inline int getIntByColID(OBJID colID, const _byte *buf) {
        return *(int*)(buf+getConstOffset()+sizeof(int)*id2idx[colID]);
    }
    inline string getStringByIndex(int idx, const _byte *buf){
     //  assert(idx>=constlen_cnt && idx<columns.size());
       idx -= constlen_cnt;
       OFFSET start = (idx==0)?getVarOffset():(*(OFFSET*)(buf+sizeof(OBJID)+(idx-1)*sizeof(OFFSET)));
       OFFSET len = *(OFFSET*)(buf+sizeof(OBJID)+(idx)*sizeof(OFFSET))-start;
       return string((char*)(buf+start),len);
    }


	inline size_t _getStringByIndexF(int idx, const _byte *buf, char *dst){
       idx -= constlen_cnt;
       OFFSET start = (idx==0)?getVarOffset():(*(OFFSET*)(buf+sizeof(OBJID)+(idx-1)*sizeof(OFFSET)));
       OFFSET len = *(OFFSET*)(buf+sizeof(OBJID)+(idx)*sizeof(OFFSET))-start;
	   memcpy(dst, buf+start, len);
       return len;  //string((char*)(buf+start),len); 
	}

    inline string getStringByColID(OBJID colID, const _byte *buf){
     //  assert(idx>=constlen_cnt && idx<columns.size());
        int idx = id2idx[colID];
        idx -= constlen_cnt;
        OFFSET start = (idx==0)?getVarOffset():(*(OFFSET*)(buf+sizeof(OBJID)+(idx-1)*sizeof(OFFSET)));
        OFFSET len = *(OFFSET*)(buf+sizeof(OBJID)+(idx)*sizeof(OFFSET))-start;
        return string((char*)(buf+start),len);
    }
    inline OBJID  getRowId(const _byte *buf){
      return *(OBJID*)buf;
    }

    inline  DataType getTypeByIndex(int idx){
        if(idx>=0 && idx<constlen_cnt) return INTEGER;
        if(idx>=constlen_cnt && idx<columns.size()) return VARCHAR;
        return UNKNOWN_TYPE;
    }
   inline DataType getTypeByOBJID(OBJID id){
	    return getTypeByIndex(id2idx[id]);
   }

    inline  string getCSVItemByIndex(int idx, const _byte *buf){
        char tbuf[64];
        switch(getTypeByIndex(idx)){
           case INTEGER: sprintf(tbuf,"%d", getIntByIndex(idx,buf)); return string(tbuf);
           case VARCHAR: {
             string tmp("'");
             tmp.append(getStringByIndex(idx,buf));
             tmp.push_back('\'');
             return tmp;
           }
           default: 
             cerr<<"ERROR:UNKNOWN_TYPE " <<__LINE__<<endl;
             return string("");
        }
    }

	inline size_t getCSVItemByIndexF(int idx, const _byte *buf, char* dst){
       switch(getTypeByIndex(idx)){
           case INTEGER: {
			 char tbuf[64];
			 sprintf(tbuf,"%d", getIntByIndex(idx,buf)); 
			 size_t len = strlen(tbuf);
			 memcpy(dst,tbuf,len);
			 return len;
						 }
           case VARCHAR: {
			  char *ptr = dst;
			  *ptr = '\'';
			  ptr++;
			  size_t len = _getStringByIndexF(idx,buf,ptr);
			  ptr += len;
			  *ptr = '\'';
			  return len+2;
             //string tmp("'");
             //tmp.append(getStringByIndex(idx,buf));
             //tmp.push_back('\'');
             //return tmp;
           }
           default: 
             cerr<<"ERROR:UNKNOWN_TYPE " <<__LINE__<<endl;
             return 0;
        }

	}

   
    inline string getCSVItemByOBJID(OBJID colid, const _byte *buf){
       //std::map< OBJID, int >::iterator it = id2idx.find(colid);
       //assert(it!=id2idx.end());
       return getCSVItemByIndex(id2idx[colid],buf);
    }

   inline void * getIndexDbByIndex(int idx){
       //std::map< OBJID, IndexDb* >::iterator it = colid2idxDb.find(colid);
       //if(it==colid2idxDb.end())return NULL;
       //return it->second;
       return idx2idxDb[idx];
   }
   inline void * getIndexDbByOBJID(OBJID cid){
	 return idx2idxDb[id2idx[cid]];
   }

   //inline void 
	inline void mergeLGCond(vector<CondInfo> &ci){
	  map<OBJID, vector<CondInfo> > id2ci;
	  for(int i=0;i<ci.size();i++){
        map<OBJID, vector<CondInfo> >::iterator it= 
		  id2ci.find(ci[i].op1);
		if(id2ci.end()!=it){
		  it->second.push_back(ci[i]);
		}else{
          id2ci[ci[i].op1] = vector<CondInfo>(1,ci[i]);
		}
	  }
	  ci.clear();
	  for(map<OBJID, vector<CondInfo> >::iterator it
		  =id2ci.begin();it!=id2ci.end();++it){
		if(it->second.size()>=2){
          IndexDbI *idb= (IndexDbI*)getIndexDbByOBJID(it->second[0].op1);
		  assert(idb);
		  int valmin = idb->getMin();
		  int valmax = idb->getMax();
		  int curmin=valmin, curmax=valmax;
		  for(int i=0;i<it->second.size();i++){
			if(it->second[i].type==CT_GREATER){
			  curmin = max(it->second[i].int_op2+1,curmin);
			}else if(it->second[i].type==CT_LESS){
			  curmax = min(it->second[i].int_op2-1,curmax);
			}else{
			  assert(0);
			}
		  }
		  CondInfo ci_new=it->second[0];
		  ci_new.type = CT_RANGE;
		  ci_new.range = pair<int,int>(curmin,curmax);
		  ci.push_back(ci_new );
		  //__dump_cond(ci);
		}else{
		  ci.push_back(it->second[0]);
		}
	  }

	}

   inline void mergeEqCond(vector<CondInfo> &ci ){
	 vector<CondInfo> lgcond;
     for(vector<CondInfo>::iterator it=ci.begin();it!=ci.end();){
       if(it->type==CT_LESS || it->type==CT_GREATER){
		 lgcond.push_back(*it);
		 it = ci.erase(it);
	   }else{
		 ++it;
	   }
	 }
	 mergeLGCond(lgcond);
     //cerr<<"####@@@@";
	 if(ci.empty()){
	   ci = lgcond;
	 }else{
	   //IdManager *im = im->getInstance();
       //MyDb* db =  id2db(ci);
	   //assert(db);
	   vector<CondInfo> tmpKey,tmp2;
	   tmpKey.resize(pkey2origrow.size());
	   int pkeycnt = 0;
       for(int i=0;i<ci.size();i++){
		 if( pkeyIdx[ci[i].op1] <0){
		    tmp2.push_back(ci[i]);
		 }else{
            tmpKey[ pkeyIdx[ci[i].op1] ] = ci[i];
			pkeycnt++;
		 }
	   }
	   if(pkeycnt==pkey2origrow.size()){
		 string tmps;
         char tbuf[64];
		 for(int i=0;i<tmpKey.size();i++){
           if(tmpKey[i].type==CT_CONST_INT){
			 sprintf(tbuf, "%d", tmpKey[i].int_op2);
			 tmps.append(string(tbuf));
		   }else if(tmpKey[i].type==CT_CONST_STRING){
			 tmps.append(tmpKey[i].str_op2);
		   }else{
		     assert(0);
		   }
		   tmps.append(",");
		 }
         ci.clear();
		 CondInfo cikey = tmpKey[0];
		 cikey.type = CT_CONST_STRING;
		 cikey.op1 = pkcolid; 
		 cikey.str_op2 = tmps;
		 ci.push_back(cikey);
		 for(int i=0;i<tmp2.size();i++)
		   ci.push_back(tmp2[i]);
		 for(int i=0;i<lgcond.size();i++)
		   ci.push_back(lgcond[i]);
	     sort(ci.begin()+1,ci.end(),cmpPredicate_slow1);
	   }else{
		 for(int i=0;i<lgcond.size();i++)
		   ci.push_back(lgcond[i]);
	     sort(ci.begin(),ci.end(),cmpPredicate_slow1);
	   }
	 }
   }

   void createIndexDbByOBJID(OBJID cid);

   inline int cid2index(OBJID cid){
      return id2idx[cid];
   }

   inline OBJID index2cid(int idx){
      return columns[idx];
   }

   inline int getRowCount(){
	 return cur_col_id-1;
   }
   //bool filterRow(const vector<CondInfo> &cond, RowHash & ht);

    bool checkRow(const vector<CondInfo> &cond, const _byte *buf);
    //int filterRowHash(const vector<CondInfo> &cond0, RowHash & out);
    int filterRowCond(const vector<CondInfo> &cond0, RowHash & out);
    inline int filterRow(const vector<CondInfo> &cond0, RowHash & out){
	  filterRowCond(cond0, out);
	}
    string getDbName() {
        return dbFileName_;
    }

    //inline getIntByOBJID()
private:
    std::string dbFileName_;
    u_int32_t cFlags_;
	_byte * data_buffer;
	_byte * data_buffer_r;
	OBJID cur_col_id;
    //std::map< OBJID, IndexDb* > colid2idxDb;
    std::vector<void *> idx2idxDb;//IndexDb
    //std::map< OBJID, int > id2idx;
	int *id2idx;
    // Make sure the default constructor is private
    // We don't want it used.
    MyDb()/* :Db(NULL,0)*/ {}
/*
    inline void _modifyRHashI(RowHash &ht, OBJID id, int n){
        if(n==0){
            ht.insert(pair<OBJID,int>(id,1));
        }else{
            RHIter it1 = ht.find(id);
            if(it1!=ht.end() && it1->second==n)
                it1->second++;
        }
    }
*/
	inline void _checkAndModifyRHash(RowHash &ht,const vector<CondInfo>& cond,
		OBJID id){
	           const _byte *dbuf;
			   fetchRow(id, &dbuf);
               if(checkRow(cond, dbuf)){
                 //ht.insert(id ); 
				 ht.push_back(id);
			   }
			   //delete[] dbuf;
	}
    // We put our database close activity here.
    // This is called from our destructor. In
    // a more complicated example, we might want
    // to make this method public, but a private
    // method is more appropriate for this example.
    void closedb();
};



inline double evalPredicate(const CondInfo  &condInfo){
  double val = 1.0;
  MyDb* rdb = id2db[condInfo.op1_table];
  //if(condInfo.empty()) return val;
      if( condInfo.type==CT_CONST_STRING ){
	  IndexDbS *idb=(IndexDbS*)rdb->getIndexDbByOBJID(condInfo.op1);
	  val = 1.0/(1+idb->distinct_cnt);
      }else if (condInfo.type == CT_CONST_INT){
          IndexDbI *idb= (IndexDbI*)rdb->getIndexDbByOBJID(condInfo.op1);
	  val = 1.0/(1+idb->distinct_cnt) ;
      } else if (condInfo.type == CT_LESS) {
          IndexDbI *idb= (IndexDbI*)rdb->getIndexDbByOBJID(condInfo.op1);
              double minval = idb->getMin();
              double maxval = idb->getMax();
		    val = (condInfo.int_op2-minval)/(0.01+maxval-minval);
      } else if ( condInfo.type == CT_GREATER) {
          IndexDbI *idb= (IndexDbI*)rdb->getIndexDbByOBJID(condInfo.op1);
              double minval = idb->getMin();
              double maxval = idb->getMax();
		    val = (maxval-condInfo.int_op2)/(0.01+maxval-minval);
          
      } else if ( condInfo.type == CT_RANGE) {
          IndexDbI *idb=(IndexDbI*)rdb->getIndexDbByOBJID(condInfo.op1);
		  assert(idb);
	      val = (condInfo.range.second-condInfo.range.first)
			/(0.01+idb->getMax()-idb->getMin());

      }else{
	  assert(0);
      }
  
  val = max(0.0,val);
  return val; 
}

inline bool cmpPredicate_slow1(const CondInfo &ci1, const CondInfo &ci2){
//  cerr<<"@@@@";
  return evalPredicate(ci1)<evalPredicate(ci2);
}

#endif
